// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CBoundNode.
//   $Id$


#include "stdafx.h"
#include "boundnode.h"

#include "bindingTarget.h"
#include "XFormsApp.h"
#include "tags.h"
#include "FP_ReturnCodes.h"

static CComBSTR g_sDate = L"date";

extern CXFormsApp theApp;
#ifdef COUNT_CBOUNDNODE
	unsigned long CBoundNode::sm_Count = 0;
#endif
CBoundNode::CBoundNode(IXMLDOMNode * pNode,IXMLDOMSchemaCollection * pSchemaCache)
	:m_pReadOnly(NULL)
	,m_pRelevant(NULL)
	,m_pRequired(NULL)
	,m_pCalculate(NULL)
	,m_pValueVertex(NULL)
	,m_bReadOnly(false)
	,m_bRelevant(true)
	,m_bRequired(false)
	,m_bRelevantInherited(true)
	,m_bReadOnlyInherited(false)
	,m_bValueChanged(false)
	,m_bCurrentConstraintState(true)
	,m_pNode(pNode)
	,m_pSchemaCache(pSchemaCache)
	,m_pSchemaType(0)
	,m_pXSISchemaType(0)
	,m_pExternalType(0)
	,m_lRefcount(1)
	,m_bNillable(false)
	,m_pBNParent(0)
{
	if(!sm_hNodemapMutex)
		sm_hNodemapMutex = CreateMutex(0, FALSE, _T("Local\\BoundNodeNodemapMutex"));

#ifdef COUNT_CBOUNDNODE
	++sm_Count;
#endif
	if(m_pNode)
		m_pNode->AddRef();
	else
		throw "pNode is NULL";
	constructSchemaRelationships();

}

CBoundNode::~CBoundNode()
{
	try
	{
		//Remove from the map of nodes / objects.
		if(m_pNode)
		{
			IUnknown * pUnk1 = NULL;
			m_pNode->QueryInterface(IID_IUnknown,(void**)&pUnk1);

			WaitForSingleObject(sm_hNodemapMutex, INFINITE);

			try
			{
				sm_Nodemap.RemoveKey(pUnk1);
			}
			catch(...)
			{
			}

			ReleaseMutex(sm_hNodemapMutex);

			::safeRelease(pUnk1);
		}
		long l = 0;
		//disconnect the targets.
		while(!m_lstTargets.IsEmpty())
		{
			try
			{
				l++;
				IBindingTarget * pTarget = m_lstTargets.RemoveHead();
				// Bug 249 - Occasionally on shutdown the target will already be deleted
				// so simily check whether the control is being shutdown, if it's not then
				// it's safe to disconnect. 
				// NOTE :: This is not the best solutions for this. Deletion of objects and removal 
				// of reference to these objects should be handle correctly.
				if(pTarget && m_pXForms && m_pXForms->canContinue())
					pTarget->disconnect(this);
				m_lstUnWittingTargets.clear();
			}

			catch(...){ASSERT(0);}
			
			//::AfxDebugBreak();
		}
		if(m_pValueVertex)
			m_pValueVertex->m_pInstanceNode = 0;
	/*
		if(m_pNode)
		{
			CComPtr<IXMLDOMNode> pParent;
			m_pNode->get_parentNode(&pParent);
			if(pParent)
			{
				CBoundNode * b = CBoundNode::GetBoundNode(pParent,0,true);
				if(b)
				{
					b->RemoveBoundChild(this);
					b->Release();
				}
				else
				{
				}
			}
		}
		*/
		if(m_pBNParent)
		{
			m_pBNParent->RemoveBoundChild(this);
			//TODO - replace the above removal stuff with m_pBNParent.
			m_pBNParent->Release();
			m_pBNParent = 0;
		}
		::safeRelease(m_pNode);
		::safeRelease(m_pSchemaType);
		::safeRelease(m_pXSISchemaType);
		::safeRelease(m_pExternalType);
	
		while(!m_lstChildren.IsEmpty())
		{
			m_lstChildren.RemoveHead()->m_pBNParent = 0;
		}
		m_lstChildren.RemoveAll();
		m_ConstraintMap.RemoveAll();
		#ifdef COUNT_CBOUNDNODE
		--sm_Count;
		#endif
	}
	catch(...)
	{
		ASSERT(0);
	}
}
void CBoundNode::getType(BSTR * psType)
{
	ISchemaType *pType = 0;
	if(m_pSchemaType)
		pType = m_pSchemaType;
	else if(m_pXSISchemaType)
		pType = m_pXSISchemaType;
	else if(m_pExternalType)
		pType = m_pExternalType;

	CComQIPtr<ISchemaItem> pItem = pType;
	if(pItem)
	{
		if(IsDateType(pItem))
			g_sDate.CopyTo(psType);
		else
			pItem->get_name(psType);
	}

/*I think all of htis is redundant
	if(m_sType.m_str)
	{
		// if bind has set a type, use this.
		//	the spec does not say what should be used, 
		//	but I think it may be illegal for a schema
		//	to set a type as well as a bind, because
		//	this is probabl;y a model item property and 
		//	as such cannot be set by more than one location.

		//This method treats it as bind takes precedent over 
		//	schema.
		m_sType.CopyTo(psType);
	}
	else
	{
		//returns the type of the node.
		CComVariant vtType;
		HRESULT hr = m_pNode->get_dataType(&vtType);
		if(hr == S_FALSE)
		{
			//if the node is untyped, the type of the 
			//	node is xsd:string
			*psType = CComBSTR("xsd:string").Detach();
		}
		else if(hr == S_OK)
		{
			// if there was a type, that is our return value.
			wcscpy(*psType,vtType.bstrVal);
		}
	}
	*/
}

bool CBoundNode::IsDateType(ISchemaItem *pSchemaItem)
{
	SOMITEMTYPE type;
	pSchemaItem->get_itemType(&type);

	if(type == SOMITEM_DATATYPE_DATE)
		return true;

	if(type == SOMITEM_SIMPLETYPE)
	{
		CComQIPtr<ISchemaType> pType = pSchemaItem;
		if(pType)
		{
			SCHEMADERIVATIONMETHOD derivationMethod;
			pType->get_derivedBy(&derivationMethod);
			if(derivationMethod == SCHEMADERIVATIONMETHOD_RESTRICTION)
			{
				CComPtr<ISchemaItemCollection> pBaseTypes;
				pType->get_baseTypes(&pBaseTypes);
				if(pBaseTypes)
				{
					long nBaseTypes = 0;
					pBaseTypes->get_length(&nBaseTypes);
					for(long i = 0; i < nBaseTypes; ++i)
					{
						CComPtr<ISchemaItem> pBaseType;
						pBaseTypes->get_item(i, &pBaseType);
						if(IsDateType(pBaseType))
							return true;
					}
				}
			}
		}
	}

	return false;
}

void CBoundNode::addTarget(IBindingTarget *pTarget,bool bDoItQuietly)
{
	//HACK: since bug 1233, bDoItQuietly is no longer relevant
	//	however, in order to get this build done quickly, I'm checking
	//	in this disablement of it, rather than removing it entirely
	//	I will remove it entirely soon.
	bDoItQuietly = false;
	
	if(pTarget) {
		//only insert the same target once.
		POSITION pos = m_lstTargets.Find(pTarget);
		if(!pos)
		{
			// Instruct the element that we are binding it to a node so that it doesn't
			// process the state events as it is handled by setInitalState()
			pTarget->bindingToNode(true);
			// Add the target to the 
			m_lstTargets.AddTail(pTarget);

			if(!bDoItQuietly)
			{
				// Setting the intial state results in the css classes that represent the form control states 
				// in one call giving better performance.
				pTarget->setInitalState(m_bRelevant && m_bRelevantInherited, m_bReadOnly || m_bReadOnlyInherited, m_bCurrentConstraintState, m_bRequired);
				//The target will probably want all of the 
				//	properties on this node, 
				m_bRelevant && m_bRelevantInherited?pTarget->onenabled():pTarget->ondisabled();
				m_bReadOnly || m_bReadOnlyInherited?pTarget->onreadonly():pTarget->onreadwrite();
				m_bCurrentConstraintState?pTarget->onvalid():pTarget->oninvalid();
				m_bRequired?pTarget->onrequired():pTarget->onoptional();
				//TODO: investigate whether this obviates some other call to onvaluechanged
				//I have just put this here to fix a bug with GE,
				//	should it be here?  It feels like it should, but since
				//	most things seem to work without it, surely in the most
				//	common cases, this will lead to duplication of effort.
				//	On the other hand, I think most controls check that 
				//	the "changed" value is indeed different, so it shouldn't
				//	be too great a performance killer.
			//	if(m_pXForms->m_bDocumentInitialised)
			//		pTarget->onvaluechanged();
			}
			// Finished binding so instruct the element so.
			pTarget->bindingToNode(false);
		}
	}
}

void CBoundNode::cloneTargetList(CPtrList & rList)
{
	POSITION pos = m_lstTargets.GetHeadPosition();
	while(pos)
	{
		rList.AddTail(m_lstTargets.GetNext(pos));
	}
}

HRESULT CBoundNode::constructSchemaRelationships()
{
 	HRESULT hr= S_FALSE;
	if(m_pNode)
	{
		IXMLDOMDocument * pDoc = 0;
		m_pNode->get_ownerDocument(&pDoc);
		if(pDoc)
		{
			IXMLDOMDocument2 * pDoc2 = 0;
			pDoc->QueryInterface(IID_IXMLDOMDocument2, (void**)&pDoc2);
			if(pDoc2)
			{
				IXMLDOMSchemaCollection  * pSchemata0 = NULL;
				hr = pDoc2->get_namespaces(&pSchemata0);
				if(pSchemata0)
				{
					IXMLDOMSchemaCollection2 * pSchemata = NULL;
					hr = pSchemata0->QueryInterface(IID_IXMLDOMSchemaCollection2,(void**)&pSchemata);
					if(pSchemata)
					{

						if(m_pSchemaCache)
						{
							//retrieve the xsi:type attribute,
							//	if any, and use that to find another
							//	ISchemaType
/*							CComVariant vtOldXMLNS;
							CComBSTR bsSelectionNamespaces(L"SelectionNamespaces");
							pDoc2->getProperty(bsSelectionNamespaces,&vtOldXMLNS);
							CComBSTR bsTempSelectionNamespaces(L"xmlns:a='http://www.w3.org/2001/XMLSchema-instance' xmlns:b='http://www.w3.org/1999/XMLSchema-instance'");
							CComVariant v(bsTempSelectionNamespaces);
							pDoc2->setProperty(bsSelectionNamespaces,v);
							CComPtr<IXMLDOMNode> pTypeAttr = 0;
							BSTR bs = ::SysAllocString(L"@a:type | @b:type");
							hr = m_pNode->selectSingleNode(bs,&pTypeAttr);
							::SysFreeString(bs);
							pDoc2->setProperty(bsSelectionNamespaces,vtOldXMLNS);
							*/

							CComPtr<IXMLDOMNamedNodeMap> pNNMap;
							m_pNode->get_attributes(&pNNMap);
							CComPtr<IXMLDOMNode> pTypeAttr;
							if(pNNMap)
							{
								pNNMap->getQualifiedItem(L"type",L"http://www.w3.org/2001/XMLSchema-instance",&pTypeAttr);
								if(!pTypeAttr)
									pNNMap->getQualifiedItem(L"type",L"http://www.w3.org/1999/XMLSchema-instance",&pTypeAttr);
							}

							if(pTypeAttr) 
							{
								//A type attribute has been retrieved,
								//	fetch the ISchemaType for it.
								CStringW wsXSIType;
								CComBSTR bsXSITypeAndNSURI;
								pTypeAttr->get_text(&bsXSITypeAndNSURI);
								wsXSIType = bsXSITypeAndNSURI;  // Same string used for XSIType and NS URI
								bsXSITypeAndNSURI = "";
								if(wsXSIType && !wsXSIType.IsEmpty())
								{
									//find the URI associated with the type
									long lColonPos = wsXSIType.Find(':');
									
									if(lColonPos > 0) //NB: > not !=
									{
										CStringW wsXPath;
										wsXPath.Format(L"namespace::%s",wsXSIType.Left(lColonPos));
										IXMLDOMNode * pNSURI = 0;
										BSTR s = wsXPath.AllocSysString();
										m_pNode->selectSingleNode(s,&pNSURI);
										::SysFreeString(s);
										if(pNSURI)
										{
											pNSURI->get_text(&bsXSITypeAndNSURI);
										}
										else
										{
											hr = E_FAIL;
											CString sErr;
											sErr.Format("Undeclared namespace prefix resolving type: %S",wsXSIType);
											theApp.reportError(sErr);
										}

										::safeRelease(pNSURI);
										wsXSIType.Delete(0,lColonPos+1);
									}
									//Don't fetch a type when the NS prefix resolution failed.
									if(SUCCEEDED(hr)) {
										//Fetch the type from the schema.
										//TODO: this probably won't work if there is 
										//	no targetNamespace in the schema.
										CComQIPtr<IXMLDOMSchemaCollection2> pSchemaCache2;
										pSchemaCache2 = m_pSchemaCache;
										if(pSchemaCache2) {
											CComPtr<ISchema> pSchema;
											pSchemaCache2->getSchema(bsXSITypeAndNSURI,&pSchema);
											if(pSchema) {
												CComPtr<ISchemaItemCollection> pTypes;
												pSchema->get_types(&pTypes);
												if(pTypes) {
													CComPtr<ISchemaItem> pItem;
													BSTR s = wsXSIType.AllocSysString();
													pTypes->itemByQName(s,bsXSITypeAndNSURI,&pItem);
													::SysFreeString(s);
													if(pItem)
														hr = pItem->QueryInterface(IID_ISchemaType,(void**)&m_pXSISchemaType);
													else {
														CString sErr;
														sErr.Format("Undeclared type: %S",wsXSIType);
														theApp.reportError(sErr);
													}
												}

											}
										}
									}
								}
							}
						}

						if(!m_pXSISchemaType)
						{
							//TODO: find out circumstances for error thrown by getDeclaration.
							//MSXML - IXMLDOMSchemaCollection2::getDeclaration sometimes mysteriously
							//	throws an error.  This is nothing to do with us, and there is nothing
							//	we can do about it.
							//
							//	Suspect schemata with import statements.  - False Suspicion
							//	Suspect xsi:type:
							//		By making this selection conditional on whether m_pXSISchemaType
							//		Has been resolved, this can be bypassed.

						
							CComPtr<ISchemaItem> pSI;
							try
							{
								hr = pSchemata->getDeclaration(m_pNode, &pSI);
							}
							catch(...)
							{
								AtlTrace("!formsplayer.dll: IXMLDOMSchemaCollection2::getDeclaration failed.\n");
								ASSERT(0);
							}

							if(SUCCEEDED(hr) && pSI)
							{
								hr = getSimpleType(pSI,&m_pSchemaType);
							}						
						}
					}
					::safeRelease(pSchemata);
				}
				::safeRelease(pSchemata0);

				
			}
			::safeRelease(pDoc2);
		}
		::safeRelease(pDoc);
		hr = S_OK;
		
		//TODO: find a way to validate against complex types.
		//Delete any complex types
		//
		CComQIPtr<ISchemaComplexType> pComplex = m_pSchemaType;
		if(pComplex)
		{
			pComplex.Release();
			::safeRelease(m_pSchemaType);
		}
		pComplex = m_pXSISchemaType;
		if(pComplex)
		{
			pComplex.Release();
			::safeRelease(m_pXSISchemaType);
		}

		//Test the current value against the 
		//	new schema constraints.

		CComBSTR sVal;
		hr = getValue(&sVal);
		if(SUCCEEDED(hr))
			testValue(sVal);

	}

	return hr;
}

HRESULT CBoundNode::putExternalType(ISchemaType * pType)
{
	HRESULT hr;
	//Ban multiple settings of this property.
	//(I think)
	if(m_pExternalType || !pType)
	{
		hr = E_FAIL;
	}
	else
	{
		m_pExternalType = pType;
		m_pExternalType->AddRef();
		CComBSTR s;
		
		//Check that the current value is still valid.
		getValue(&s);
		VARIANT_BOOL bValid;
		m_pExternalType->isValid(s,&bValid);
		
		//Set the constraint values based on this type.
		if(bValid)
			setConstraint(0,true);
		else
			setConstraint(0,false);
		hr = S_OK;
	}

	return hr;
}

HRESULT CBoundNode::AddPropertyVertex(CVertex * pVertex, CVertex::EType eType)
{
	HRESULT hr = S_FALSE;
	CVertex ** ppToSet = 0;
	switch(eType)
	{
		case CVertex::EType::Calculate:
				ppToSet = &m_pCalculate;
				if(pVertex)// && !m_pReadOnly)
					m_bReadOnly = true;
				break;
		case CVertex::EType::Readonly:
				ppToSet = &m_pReadOnly;
				break;
		case CVertex::EType::Relevant:
				ppToSet = &m_pRelevant;
				break;
		case CVertex::EType::Required:
				ppToSet = &m_pRequired;
				break;
		case CVertex::EType::Instance:
				ppToSet = &m_pValueVertex;
				if(!pVertex)
				{	
					//detach all the targets.
					long l = 0;
					//disconnect the targets.
					while(!m_lstTargets.IsEmpty())
					{
						l++;
						IBindingTarget * pTarget = m_lstTargets.RemoveHead();
						// Bug 249 - Occasionally on shutdown the target will already be deleted
						// so simily check whether the control is being shutdown, if it's not then
						// it's safe to disconnect. 
						// NOTE :: This is not the best solutions for this. Deletion of objects and removal 
						// of reference to these objects should be handle correctly. 

						if(pTarget && m_pXForms && m_pXForms->canContinue())
							pTarget->disconnect(this);
					}
					if(!m_lstUnWittingTargets.empty())
						m_lstUnWittingTargets.clear();
					
				}
				break;
		case CVertex::EType::Constraint:
				if(pVertex)
					m_ConstraintMap.SetAt(pVertex,(void*)TRUE);
			break;
	}

	if(ppToSet)
	{	
		if(*ppToSet && pVertex)
		{
			//This is an error, you are trying to set a property that is already set.
			//fire xforms-compute-exception
			hr = FP_E_DUPLICATE_PROPERTY;
			wchar_t * bsAttrName = 0;
			
			switch(eType)
			{
				case CVertex::EType::Calculate:
						bsAttrName = ATTR_CALCULATE.m_str;
						break;
				case CVertex::EType::Readonly:
						bsAttrName = ATTR_READONLY.m_str;
						break;
				case CVertex::EType::Relevant:
						bsAttrName = ATTR_RELEVANT.m_str;
						break;
				case CVertex::EType::Required:
						bsAttrName = ATTR_REQUIRED.m_str;
						break;
			}
			SetupErrorInfo(hr,bsAttrName);
			
		}
		else
		{
			//This is fine, this is the first instance of this property 
			//on this node, store the pointer to it.
			*ppToSet = pVertex;
			hr = S_OK;
		}
	}
	return hr;
}

HRESULT CBoundNode::setValue(VARIANT vtVal)
{
	HRESULT hr = S_OK;
	switch(vtVal.vt)
	{
	case VT_BSTR:
		hr = setValue(vtVal.bstrVal);
//TODO: sort out the subgraph
//		m_pModel->m_arLc.AddHead(m_pValueVertex);
	//TODO: add other cases, get from putBoundData.
	default:
		hr = E_NOTIMPL;
	}
	return hr;
	
}
const bool g_bLiveUpdate = false;

HRESULT CBoundNode::setValue(IXMLDOMDocumentFragment * pVal,bool/* bOverride*/)
{
	//should be S_FALSE?
	HRESULT hr = E_FAIL;
	

	bool bIsCurrentlyReadOnly = m_bReadOnly || m_bReadOnlyInherited;
	if(!bIsCurrentlyReadOnly)
	{
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();
	
		hr = m_pNode->put_text(0);
		if(SUCCEEDED(hr))
		{
			hr = m_pNode->appendChild(pVal,NULL);

			// Need to normalize node so that values entered by selects are picked 
			// up by other controls. Otherwise the getValue method returns an blank 
			// string.
			CComQIPtr<IXMLDOMElement> pEl = m_pNode;
			if(pEl)
				pEl->normalize();
			
			CComBSTR sValue;
			m_pNode->get_text(&sValue);
			if(sValue)
			{
				testValue(sValue);
			}

			if(m_pRequired)
			{
				VARIANT_BOOL bChildren;
				m_pNode->hasChildNodes(&bChildren);
				if(m_bRequired && !bChildren)
				{
					setConstraint(m_pRequired,false);
				}
				else
				{
					setConstraint(m_pRequired,true);
				}
			}
		}


		//put the vertex into the pertinent subgraph.
		//This should be more.  We have changed the
		//	tree, so rebuild is needed.
		if(m_pValueVertex)
			m_pValueVertex->makePertinent();

		m_bValueChanged = true;

		if(g_bLiveUpdate)
			updateTargetValues();
		else
		{
			try {
				POSITION pos = m_lstTargets.GetHeadPosition();
				while(pos)
					m_lstTargets.GetNext(pos)->onvaluechanged();
					//alert all the controls
			}
			catch(...) {
				ASSERT(0);
			}
		}
		//unlock
		Release();

	}
	return hr;
}

HRESULT CBoundNode::setValue(BSTR const sVal,bool bOverride)
{
	HRESULT hr = E_FAIL;
	BSTR sBuf = ::SysAllocString(sVal);  // Bug 106, IXMLDOMNode::get_text corrupts CComBSTR on Windows XP.

	bool bIsCurrentlyReadOnly = m_bReadOnly || m_bReadOnlyInherited;

	if(bOverride || !bIsCurrentlyReadOnly)
	{
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();

		CComBSTR sCurrentVal;
		m_pNode->get_text(&sCurrentVal);
		//== is overrriden on CComBSTR, I don't think != is.
		if(sCurrentVal == sBuf)
		{
			//do nothing
		}
		else
		{
			hr = testValue(sBuf);
			if(S_OK == hr)
			{
				hr = m_pNode->put_nodeTypedValue(_variant_t(sBuf));
				if(S_OK != hr)
					hr = m_pNode->put_text(sBuf);
			}
			//S_FALSE is returned when the value cannot be tested 
			//	against SOM schematypes.
			else if(S_FALSE == hr)
			{
				hr = m_pNode->put_nodeTypedValue(_variant_t(sBuf));
				
				if(FAILED(hr))
				{
					//we still want to be able to insert invalid content
					//	then notify the caller that we have done so. 
					hr = m_pNode->put_text(sBuf);

					//set the implicit validity constraint based on type.
					setConstraint(0,false);
				}
				else
				{
					//set the implicit validity constraint based on type.
					setConstraint(0,true);
				}
			}
		
			//put the vertex into the pertinent subgraph.
			if(m_pValueVertex)
				m_pValueVertex->makePertinent();
		
			m_bValueChanged = true;

			if(g_bLiveUpdate)
				updateTargetValues();
			else
			{
				try {
					POSITION pos = m_lstTargets.GetHeadPosition();
					while(pos)
						m_lstTargets.GetNext(pos)->onvaluechanged();
						//alert all the controls
				}
				catch(...) {
					ASSERT(0);
				}
			}
		}
		Release();
	}
	::SysFreeString(sBuf);

	return hr;
}

bool CBoundNode::isOneOfType(const SQName qnTypes[],long lCount)
{
	bool bRet = false;

	ISchemaType * arrTypes[] = {m_pSchemaType,m_pExternalType,m_pXSISchemaType};
	long l = 0;
	
	//first just loop through, checking the direct types.
	ISchemaType * pType = 0;
	for(l = 0;l<3;++l)
	{
		pType = arrTypes[l];
		if(pType)
		{
				bRet = compareType(pType,qnTypes,lCount);
				if(bRet)
					break;
		}
	}
	// We only need to check the based datatypes if the direct 
	// type doesn't match.
	if(!bRet) {
		//Now do the recursive loop through baseTypes
		for(l = 0;l<3;++l)
		{
			pType = arrTypes[l];
			if(pType)
			{
				bRet= derivedFromType(pType,qnTypes,lCount);
				if(bRet)
					break;
			}
		}
	}

	return bRet;
}
//TODO: reduce text comparisons by passing lists of name strings
//	all with the same Namespace URI
//TODO: probably move these type comparison 
//	functions to a different  file.

// compares a schemaType to a Unique type name to see if it matches.  called by isOfType
bool CBoundNode::compareType(ISchemaType* pType , const SQName qnTypes[],long lCount)
{
	bool bRet = false;
	CComQIPtr<ISchemaItem> pItem = pType;
	//check name first as it is likely to be smaller.
	//	also, it is more likely to be unique, 
	//	given one schema, many types will have the same URI.
	CComBSTR s;
	pItem->get_name(&s);
	
	for(long l = 0;l < lCount;++l)
	{
		if(s == qnTypes[l].bsName)
		{
			//A match, now make sure it is in the right namespace
			pItem->get_namespaceURI(&s);
			if(s == qnTypes[l].bsNSURI)
			{
				bRet = true;
				break;
			}
		}
	}
	return bRet;
}

// recursively checks base types of a type to see if they are of teh requested type.
bool CBoundNode::derivedFromType(ISchemaType* pType, const SQName qnTypes[],long lCount)
{
	bool bRet = false;
	if(pType)
	{
		//The only true inheritance is derived by restriction.
		//	We may wish to check for other derivation methods,
		//	being more flexible, but There is no current reason to.
		SCHEMADERIVATIONMETHOD eMethod;		
		pType->get_derivedBy(&eMethod);
		if(eMethod == SCHEMADERIVATIONMETHOD_RESTRICTION)
		{
			CComPtr<ISchemaItemCollection> pColl;
			pType->get_baseTypes(&pColl);
			if(pColl)
			{
				long l;
				pColl->get_length(&l);
				CComPtr<ISchemaItem> pItem;
				// loop through the base types, checking each until either success
				//	or we run out.
				while(!bRet && --l >=0)
				{
					pColl->get_item(l,&pItem);
					if(pItem)
					{
						CComQIPtr<ISchemaType> pBaseType = pItem;
						if(pBaseType)
						{
							bRet = compareType(pBaseType,qnTypes,lCount);
							//If the base type is not exactly the type
							//	we are looking for, it my be derived from
							//	the type we are looking for.

							//I don't think baseTypes is already recursive
							//	but may need to check this.
							if(!bRet)
							{
								bRet = derivedFromType(pBaseType,qnTypes,lCount);
							}
						}
					}
				}
			}
		}
		else
		{
			//Attempt to resolve via simple content (if any)
			CComQIPtr<ISchemaComplexType> pComplexType = pType;
			if(pComplexType)
			{
				CComPtr<ISchemaModelGroup> pModelGroup;
				pComplexType->get_contentModel(&pModelGroup);
				if(pModelGroup)
				{
					CComPtr<ISchemaItemCollection> pContentColl;
					pModelGroup->get_particles(&pContentColl);
					long lItems;
					pContentColl->get_length(&lItems);
					//only if there is a single content item,
					//can we progress along this route.
					if(lItems == 1)
					{
						CComPtr<ISchemaItem> pItem;
						pContentColl->get_item(0,&pItem);
						CComQIPtr<ISchemaType> pContentType = pItem;
						if(pContentType)
						{
							bRet = derivedFromType(pContentType,qnTypes,lCount);
						}
					}

				}

			}
		}
	}
	return bRet;
}

// Given an ISchemaItem that corresponds to a type, gets the appropriate simple ISchemaType 
HRESULT CBoundNode::getSimpleType(ISchemaItem * pItem , ISchemaType ** ppSchemaType)
{
	HRESULT hr = S_FALSE;

	ISchemaType * pSchemaType = 0;
	//check that type is a simpletype 
	//	(no support for validation of complex types in SOM)
	SOMITEMTYPE typeName ;
	pItem->get_itemType(&typeName);

	//	All numbers below SOMITEM_COMPLEXTYPE are
	//	various simple types apart from attribute.
	if(typeName < SOMITEM_COMPLEXTYPE && typeName != SOMITEM_ATTRIBUTE)
	{
		//It is plausible that an XPath might
		//	take us straight to the
		//	 value of a node (//x/text())
		pItem->QueryInterface(IID_ISchemaType,(void**)&pSchemaType);
		hr = S_OK;
	}
	else
	{
		if(SOMITEM_ELEMENT==typeName)
		{
			CComQIPtr<ISchemaElement> pSEL(pItem);
			if(pSEL)
			{
				pSEL->get_isNillable(&m_bNillable);
				hr = pSEL->get_type(&pSchemaType);
			}
		}
		else if(SOMITEM_ATTRIBUTE==typeName)
		{	
			CComQIPtr<ISchemaAttribute> pSAttr(pItem);
			if(pSAttr)
				hr = pSAttr->get_type(&pSchemaType);
		}
		//Having gotten a schemaType, make sure that it 
		//	is a simpleType.

		//I think that this is the correct treatment.
		//	according to the table in the documentation
		//	All numbers below SOMITEM_COMPLEXTYPE are
		//	various simple types
		if(
			pSchemaType 
			&& SUCCEEDED(pSchemaType->get_itemType(&typeName))
			&& typeName >= SOMITEM_COMPLEXTYPE
			)
		{
			//Check the content model, if it consists of
			//	one simpleType, then we are more than likely
			//	dealing with an otherwise simpleType element
			//	with attributes
			CComQIPtr<ISchemaComplexType> pComplex = pSchemaType;
			::safeRelease(pSchemaType);
			if(pComplex)
			{
				CComPtr<ISchemaItemCollection> pBaseTypes;
				pComplex->get_baseTypes(&pBaseTypes);
				if(pBaseTypes)
				{
					long l;
					pBaseTypes->get_length(&l);
					CComPtr<ISchemaItem> pBaseType;
					for (long i = 0;i < l ; ++i)
					{
						pBaseTypes->get_item(i,&pBaseType);
						pBaseType->get_itemType(&typeName);
						if(typeName < SOMITEM_COMPLEXTYPE)
						{
							//It is plausible that an XPath might
							//	take us straight to the
							//	 value of a node (//x/text())
							pBaseType->QueryInterface(IID_ISchemaType,(void**)&pSchemaType);
							hr = S_OK;
							break;
						}
					}

				}
			}

		}
	}

	*ppSchemaType = pSchemaType;
	return hr;
}


// Tests the suitability of a value based on the three schemaType members.
HRESULT CBoundNode::testValue(const BSTR & sVal)
{
//	AddRef();
	HRESULT hr = S_FALSE;
	if(m_bNillable && ::isEmpty(sVal))
	{
			setConstraint(0,true);
	}
	else if(m_pSchemaType || m_pXSISchemaType || m_pExternalType)
	{
		hr = S_OK;
		VARIANT_BOOL bValid = VARIANT_TRUE;
		if(m_pSchemaType)
			m_pSchemaType->isValid(sVal, &bValid);

		if(bValid == VARIANT_TRUE)

		{
			if(m_pXSISchemaType)
			{
				m_pXSISchemaType->isValid(sVal, &bValid);
			}			
			if(bValid == VARIANT_TRUE)
			{
				if(m_pExternalType)
					m_pExternalType->isValid(sVal, &bValid);
				if(bValid == VARIANT_TRUE)
					//set the implicit validity constraint based on type.
					setConstraint(0,true);
				else
					//set the implicit validity constraint based on type.
					setConstraint(0,false);
			}
			else
				//set the implicit validity constraint based on type.
				setConstraint(0,false);
		}
		else
		{
			//set the implicit validity constraint based on type.
			setConstraint(0,false);
		}
	}

	//set the implicit validity constraint based on the required property.
	//	Note that this is not an else if,   we need to do this, whatever
	//	the outcome of other validation
	if(m_pRequired)
	{
		if(m_bRequired && isEmpty(sVal))
		{
			setConstraint(m_pRequired,false);
		}
		else
		{
			setConstraint(m_pRequired,true);
		}
	}
//	Release();
	return hr;
}

//The following three are the same, except for the 
//	function called in the targets.  TODO: amalgamate
void CBoundNode::setReadOnly(bool bReadOnly,bool bInheriting)
{
	//	readonly truth table
	//	
	//	|	self	|	parent	| value
	//	|	1		|	0		|	1
	//	|	0		|	1		|	1
	//	|	1		|	1		|	1
	//	|	0		|	0		|	0

	bool bIsCurrentlyReadOnly = m_bReadOnly || m_bReadOnlyInherited;

	//set the appropriate member variable (own or inherited value)
	if(bInheriting) 
	{
		m_bReadOnlyInherited = bReadOnly;
	}
	else
	{
		m_bReadOnly = bReadOnly;
	}
#ifdef _DEBUG
	if(!bIsCurrentlyReadOnly && m_bReadOnly)
	{
		if(false)
		{
			CComBSTR s;
			m_pNode->get_xml(&s);
			::MessageBoxW(0,s,0,0);
		}
	}
#endif
	//Now that we have set the internal values, 
	//	perform the reeadonly test again.  

	bool bIsBecomingReadOnly = m_bReadOnly || m_bReadOnlyInherited;

	if(bIsCurrentlyReadOnly != bIsBecomingReadOnly)
	{
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();
		if(bIsBecomingReadOnly)
		{
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				m_lstTargets.GetNext(pos)->onreadonly();
				//alert all the controls
			}
		}
		else
		{
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				m_lstTargets.GetNext(pos)->onreadwrite();
				//alert all the controls
			}
		
		}
		//Inform all children that the readonly condition 
		//	has changed on the parent
		POSITION pos = m_lstChildren.GetHeadPosition();
		while(pos)
		{ 
			m_lstChildren.GetNext(pos)->setReadOnly(bIsBecomingReadOnly,true);
		}

		//unlock
		Release();
	}

	//Set the appropriate member value to the given value.

	if(bInheriting)
	{
		m_bReadOnlyInherited = bReadOnly;
	}
	else
	{
		m_bReadOnly = bReadOnly;
	}


}

void CBoundNode::setRelevant(bool bRelevant,bool bInheriting)
{
	//	relevance truth table
	//	
	//	|	self	|	parent	| value
	//	|	1		|	0		|	0
	//	|	0		|	1		|	0
	//	|	1		|	1		|	1
	//	|	0		|	0		|	0
	bool bIsCurrentlyRelevant = m_bRelevant && m_bRelevantInherited;
	
	//set the appropriate member variable (own or inherited value)
	if(bInheriting)
		m_bRelevantInherited = bRelevant;
	else
		m_bRelevant = bRelevant;

	//Now that we have set the internal values, 
	//	perform the relvance test again.  
	bool bIsBecomingRelevant = m_bRelevant && m_bRelevantInherited;

	if(bIsCurrentlyRelevant != bIsBecomingRelevant) {
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();

		if(bIsBecomingRelevant) {
			try {
				POSITION pos = m_lstTargets.GetHeadPosition();
				while(pos)
					m_lstTargets.GetNext(pos)->onenabled();
					//alert all the controls
			}
			catch(...) {
				ASSERT(0);
			}
		} else {
			try {
				POSITION pos = m_lstTargets.GetHeadPosition();
				while(pos)
					m_lstTargets.GetNext(pos)->ondisabled();
					//alert all the controls
			}
			catch(...) {
				ASSERT(0);
			}
		}

		try {
			//Inform all children that the relevance condition 
			//	has changed on the parent
			POSITION pos = m_lstChildren.GetHeadPosition();
			while(pos)
				m_lstChildren.GetNext(pos)->setRelevant(bIsBecomingRelevant,true);
		}
		catch(...) {
				ASSERT(0);
		}

		//unlock
		Release();
	}
}

void CBoundNode::setRequired(bool bRequired)
{
	if(m_bRequired != bRequired)
	{
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();
		m_bRequired = bRequired;
		if(m_bRequired)
		{
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				m_lstTargets.GetNext(pos)->onrequired();
				//alert all the controls
			}
		}
		else
		{
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				m_lstTargets.GetNext(pos)->onoptional();
				//alert all the controls
			}
		}

		// Set the implicit validity constraint generated by
		//	the presence of a required constraint.
		//	 This same thing should be done in setValue
		//	 so that if required is already true and 
		//	value goes to empty, we can make invalid.
		if(m_bRequired)
		{
			CComBSTR sText;
			HRESULT hr = m_pNode->get_text(&sText);
			if(SUCCEEDED(hr) && isEmpty(sText))
			{
				setConstraint(m_pRequired,false);
			}
			else
			{		
				setConstraint(m_pRequired,true);
			}
		}
		else
		{
			setConstraint(m_pRequired,true);
		}
		//unlock
		Release();
	}
}

HRESULT CBoundNode::setConstraint(CVertex * pConstraint,bool bValue)
{
	//TODO: add some kind of deferred processing - 
	//	setValue can call this twice.  once for schema validity
	//	and once for requiredness.
	//	I don't know how frequently this will occur, or whether it matters.
	//	I think it will only be significant if there are a large number of 
	//	constraints, and a large number of controls bound to a node.

	HRESULT hr = S_OK;

	m_ConstraintMap.SetAt(pConstraint,(void*)(bValue?TRUE:FALSE));

	bool bNewConstraintState = bValue;

	//Check to see if there is a constraint state set to false.

	//We only need to run this loop if we are setting to true, as
	//	if we are setting to false, we already know that there is a 
	//	false in the list.
	if(bValue)
	{
		POSITION pos = m_ConstraintMap.GetStartPosition();
		BOOL BCheckVal;
		CVertex * pKey = 0;
		while(pos)
		{
			m_ConstraintMap.GetNextAssoc(pos,(void*&)pKey,(void*&)BCheckVal);
			if(BCheckVal==FALSE)
			{
				//As soon as we hit a false value, we can break.
				//	one false overrides any trues in the pack.
				bNewConstraintState = false;
				break;
			}
		}
	}

	//Only if we have changed the constraint state, should we 
	//	alert the UI.
	//This section is the same as the simple bool settings
	if(bNewConstraintState != m_bCurrentConstraintState)
	{
		//Lock it, events fired by this method
		//  may otherwise cause deletion of this
		AddRef();
		m_bCurrentConstraintState = bNewConstraintState;
		if(m_bCurrentConstraintState)
		{
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				//alert all the controls
				m_lstTargets.GetNext(pos)->onvalid();
			}
		}
		else
		{
#ifdef _DEBUG
		if(false)
		{
			CComBSTR s;
			m_pNode->get_xml(&s);
			::MessageBoxW(0,s,0,0);
		}
#endif
			POSITION pos = m_lstTargets.GetHeadPosition();
			while(pos)
			{
				//alert all the controls
				m_lstTargets.GetNext(pos)->oninvalid();
			}
		
		}
		//unlock
		Release();
	}

	return hr;
}

void DumpNodeMap(CMapPtrToPtr * map)
{
	CComBSTR sMap;
	POSITION pos = map->GetStartPosition();
	void *vdK, *vdV;
	while(pos)
	{
		map->GetNextAssoc(pos,vdK,vdV);
		CBoundNode * pBN = (CBoundNode*)vdV;
		CComPtr<IXMLDOMNode> pN;
		pBN->getNode(&pN);
		CComBSTR sXML,sType;
		pN->get_nodeTypeString(&sType);
		sMap.Append(sType);
		sMap.Append("\n");
		pN->get_xml(&sXML);
		sMap.Append(sXML);
		sMap += L"\n\n";
	}
	::MessageBoxW(0,sMap,0,0);
}
//Multiton implementation:
CBoundNode * CBoundNode::GetBoundNode(IXMLDOMNode *pXMLNode,IXMLDOMSchemaCollection * pSchemaCache, bool bDoNotCreate)
{
	//Looks up the bound node in the member map, if none exists, 
	//	creates it and adds it to the map.
	CBoundNode * pBNReturn = 0;
	bool bMutexIsLocked = false;

	try
	{
		if(pXMLNode)
		{
			//Get the IUnknown pointer, we will use this to compare nodes.
			IUnknown * pUnk1 = NULL;
			pXMLNode->QueryInterface(IID_IUnknown,(void**)&pUnk1);
			
			WaitForSingleObject(sm_hNodemapMutex, INFINITE);
			bMutexIsLocked = true;

			if(!sm_Nodemap.Lookup((void*)pUnk1,(void*&)pBNReturn) && !bDoNotCreate)
			{
				// lookup has failed.  create a new one.
				//This will be destroyed in the destructor of CVertexList
				pBNReturn = new CBoundNode(pXMLNode,pSchemaCache);
				//Add it to the map to allow us to look it up and/or delete it later
				sm_Nodemap.SetAt(pUnk1,pBNReturn);

				bMutexIsLocked = false;
				ReleaseMutex(sm_hNodemapMutex);

				//Associate it with its parent, in order to inherit properties.
				CComPtr<IXMLDOMNode> pParent;
				pXMLNode->get_parentNode(&pParent);
				if(pParent)
				{
#ifdef _DEBUG
					{
						CComBSTR s;
						pXMLNode->get_baseName(&s);
#endif
						pBNReturn->m_pBNParent = CBoundNode::GetBoundNode(pParent,pSchemaCache,bDoNotCreate);
#ifdef _DEBUG
						if(false)
						{
							pParent->get_xml(&s);
							::MessageBoxW(0,s,0,0);
							pXMLNode->get_xml(&s);
							::MessageBoxW(0,s,0,0);
						}
					}
					if(false)
					{
						DumpNodeMap(&sm_Nodemap);
					}
#endif
					if(pBNReturn->m_pBNParent)
					{
						pBNReturn->m_pBNParent->addBoundChild(pBNReturn);
					}

				}

			} else {
				if(pBNReturn)
					pBNReturn->AddRef();
			}

			//clean up pUnk
			::safeRelease(pUnk1);
		}
	}
	catch(...)
	{
	}

	if(bMutexIsLocked)
		ReleaseMutex(sm_hNodemapMutex);

	return pBNReturn;
} 

CMapPtrToPtr CBoundNode::sm_Nodemap;
HANDLE CBoundNode::sm_hNodemapMutex = 0;

HRESULT CBoundNode::validate(void)
{
	HRESULT hr = S_FALSE;
	//NOTE: This test is performed  inside testValue,
	//	however, we don't want to bother getting the text
	//	out unless we are going to use it.
	//	m_pNode could be the document node, 
	//	which may contain obscene amounts of text,
	//	none of which is relevant.
	if(m_pSchemaType || m_pXSISchemaType || m_pExternalType)
	{
		CComBSTR s;
		m_pNode->get_text(&s);
		hr = testValue(s);
	}
	return hr;
}

void CBoundNode::CreateUnwittingTargetList()
{
	POSITION pos = m_lstTargets.GetHeadPosition();
	while(pos)
	{
		m_lstUnWittingTargets.insert(m_lstTargets.GetNext(pos));
	}
}

bool CBoundNode::TargetUnwittingOfValueChanged(IBindingTarget * pTarget)
{
	bool bReturn = false;
	if(m_lstUnWittingTargets.empty())
	{
		CreateUnwittingTargetList();
		bReturn = true;
	}
	else
	{
		BindingTargetList::iterator i = m_lstUnWittingTargets.find(pTarget);
		bReturn = (i != m_lstUnWittingTargets.end());
	}
	return bReturn;
}

void CBoundNode::RemoveUnwittingTarget(IBindingTarget * pTarget)
{
	BindingTargetList::iterator i = m_lstUnWittingTargets.find(pTarget);
	
	if(i != m_lstUnWittingTargets.end())
		m_lstUnWittingTargets.erase(i);

	if(m_lstUnWittingTargets.empty())
			m_bValueChanged = false;

}

void CBoundNode::UpdateIndividualTarget(IBindingTarget * pTarget)
{
	if(pTarget)
	{
		if(m_bValueChanged && TargetUnwittingOfValueChanged(pTarget))
		{
			pTarget->onvaluechanged();
			RemoveUnwittingTarget(pTarget);
		}

		m_bRelevant && m_bRelevantInherited?pTarget->onenabled():pTarget->ondisabled();
		m_bReadOnly || m_bReadOnlyInherited?pTarget->onreadonly():pTarget->onreadwrite();
		m_bCurrentConstraintState?pTarget->onvalid():pTarget->oninvalid();
		m_bRequired?pTarget->onrequired():pTarget->onoptional();
	}
}

// <summary>Dispatches onvalueChanged to all targets, suggesting that they should update their values.</summary>
void CBoundNode::updateTargetValues(void)
{
	POSITION pos = m_lstTargets.GetHeadPosition();
	//This check for IsEmpty may  be obsolete
	//	now that the check for pos equality is in place.
	while(pos && !m_lstTargets.IsEmpty())
	{
		//alert all the controls
		//Don't call getNext here, onvaluechanged
		//	may unbind the next bound control.
		//	It may also unbind and rebind the current one.
		POSITION pos2 = pos;
		IBindingTarget * pControl = m_lstTargets.GetAt(pos);
		//get the candidate next position for the next iteration.
		m_lstTargets.GetNext(pos2);
		UpdateIndividualTarget(pControl);

		//get the next position for the next iteration.
		m_lstTargets.GetNext(pos);
		//Compare the candidate and real values for pos.
		// If GetNext, using the same value for pos
		//	returns a different result,  it means that 
		//	this has been rebuilt during onvaluechanged processing.
		//	Stop running through, as this could lead to an
		//	infinite loop or a crash.
		if(pos != pos2)
			break;
	}
			
}

//When defined, this will cause the shutdown to completely clear stored nodes
//	leaving it undefined will expose memory leaks if more than one lost pointer
//	exists for any BoundNode object.  As such, undefining it is a debug step.
#define CLEAR_STORED_NODES_COMPLETELY 1 

void CBoundNode::ClearStoredNodes(void)
{
	//This function is a trap-all on shutdown.
	//	The while loop should not normally be entered.

	WaitForSingleObject(sm_hNodemapMutex, INFINITE);

	try
	{
		POSITION pos = sm_Nodemap.GetStartPosition();
		
		while(!sm_Nodemap.IsEmpty())
		{
			AtlTrace("\n!'formsplayer.dll': deleting CBoundNode on shutdown.  Check balance of AddRef/Release.\n");
			void * pUnk = 0;
			try
			{
				CBoundNode * pBN = 0;
				sm_Nodemap.GetNextAssoc(pos,pUnk,reinterpret_cast<void*&>(pBN));
				//FUDGE:
				//	if MSXML has already shut down, (which is likely),
				//	m_pNode and m_pSchemaCache will be dead.
				pBN->m_pNode = 0;
				pBN->m_pSchemaCache.p = 0;

#ifdef CLEAR_STORED_NODES_COMPLETELY 
				while(
#endif
					pBN->Release() > 0
#ifdef CLEAR_STORED_NODES_COMPLETELY 
				)
#endif
				;

			}
			catch(...)
			{
				AtlTrace("\n!'formsplayer.dll': Error in deleting CBoundNode on shutdown.\n");
			}
			sm_Nodemap.RemoveKey(pUnk);
		}
	}
	catch(...)
	{
	}

	ReleaseMutex(sm_hNodemapMutex);
}

// <summary>removes the constraint vertex pConstraintVertex from the list of constraint vertices</summary>
HRESULT CBoundNode::RemoveConstraint(CVertex * pConstraintVertex)
{
	BOOL b = m_ConstraintMap.RemoveKey(pConstraintVertex);
	return b?S_OK:S_FALSE;
}


void CBoundNode::putP3PType(BSTR sP3PType)
{
	m_sP3PType = sP3PType;
}

HRESULT CBoundNode::getP3PType(BSTR * pbsType)
{
	HRESULT hr = S_FALSE;
	if(!::isEmpty(m_sP3PType))
	{
		m_sP3PType.CopyTo(pbsType);
		hr = S_OK;
	}
	return hr;
}
